
package storage

import (
	"sync"

	pb "go.etcd.io/etcd/api/v3/etcdserverpb"
	"go.etcd.io/etcd/server/v3/config"
	"go.etcd.io/etcd/server/v3/storage/backend"

	humanize "github.com/dustin/go-humanize"
	"go.uber.org/zap"
)

const (
	// DefaultQuotaBytes是后端大小在超过空间配额之前可能消耗的字节数
	// 。
	DefaultQuotaBytes = int64(2 * 1024 * 1024 * 1024) // 2GB 
	// MaxQuotaBytes是建议用于后端
	// 配额的最大字节数。较大的配额可能会导致性能下降。
	MaxQuotaBytes = int64(8 * 1024 * 1024 * 1024) // 8GB 
)

// 配额表示针对任意请求的任意配额。每个请求
// 都需要一些费用；如果剩余费用不足，则
// 配额内的可用资源太少，无法应用请求。
type Quota interface {
	// 检测此次请求是否能通过限流，即 当 前请求执行后 ， 未达到系统负载的上限， 也就未触发F且流
	Available(req interface{}) bool
	// 计算此次请求所产生的负载，，该方法主要在 Available （）方法中调用
	Cost(req interface{}) int
	// 当前系统所能支持的剩余负裁量
	Remaining() int64
}

type passthroughQuota struct{}

func (*passthroughQuota) Available(interface{}) bool { return true }
func (*passthroughQuota) Cost(interface{}) int       { return 0 }
func (*passthroughQuota) Remaining() int64           { return 1 }

// 结构体 backendQuota 是 Quota 接 口的实现之一 ， 它主要用于限制底层 BoltDB 中的数据量，
// 其中封装了当前节点的 EtcdServer 实例(即s字段 ）和 BoltDB 数据量的上限值（ maxBackendBytes
// 字段）。
type BackendQuota struct {
	be              backend.Backend
	maxBackendBytes int64
}

const (
	// leaseOverhead是存储租约成本的估计值
	leaseOverhead = 64
	// kvOverhead是存储密钥元数据成本的估计值
	kvOverhead = 256
)

var (
	// 只记录一次
	quotaLogOnce sync.Once

	DefaultQuotaSize = humanize.Bytes(uint64(DefaultQuotaBytes))
	maxQuotaSize     = humanize.Bytes(uint64(MaxQuotaBytes))
)

// NewBackendQuota使用给定的存储限制创建配额层。
// NewBackendQuota（） 函数，其中会根据传入的参数初始化相应的 Quota 实例，
func NewBackendQuota(cfg config.ServerConfig, be backend.Backend, name string) Quota {
	lg := cfg.Logger
	quotaBackendBytes.Set(float64(cfg.QuotaBackendBytes))

	if cfg.QuotaBackendBytes < 0 {
		// 如果为负，则禁用配额
		quotaLogOnce.Do(func() {
			lg.Info(
				"disabled backend quota",
				zap.String("quota-name", name),
				zap.Int64("quota-size-bytes", cfg.QuotaBackendBytes),
			)
		})
		// 创建 passthroughQuota 实例， 它也是 Quota 接口的实现之一 ，但它并没有实现限流的功能，
		// 其 Available （）方法始终返回 true , Cost （）方法始终返回 false
		return &passthroughQuota{}
	}

	if cfg.QuotaBackendBytes == 0 {
		// 如果没有配额大小，则使用默认大小给定
		quotaLogOnce.Do(func() {
			if lg != nil {
				lg.Info(
					"enabled backend quota with default value",
					zap.String("quota-name", name),
					zap.Int64("quota-size-bytes", DefaultQuotaBytes),
					zap.String("quota-size", DefaultQuotaSize),
				)
			}
		})
		quotaBackendBytes.Set(float64(DefaultQuotaBytes))
		return &BackendQuota{be, DefaultQuotaBytes}	// 创建 backendQuota 实例
	}

	quotaLogOnce.Do(func() {
		if cfg.QuotaBackendBytes > MaxQuotaBytes {
			lg.Warn(
				"quota exceeds the maximum value",
				zap.String("quota-name", name),
				zap.Int64("quota-size-bytes", cfg.QuotaBackendBytes),
				zap.String("quota-size", humanize.Bytes(uint64(cfg.QuotaBackendBytes))),
				zap.Int64("quota-maximum-size-bytes", MaxQuotaBytes),
				zap.String("quota-maximum-size", maxQuotaSize),
			)
		}
		lg.Info(
			"enabled backend quota",
			zap.String("quota-name", name),
			zap.Int64("quota-size-bytes", cfg.QuotaBackendBytes),
			zap.String("quota-size", humanize.Bytes(uint64(cfg.QuotaBackendBytes))),
		)
	})
	return &BackendQuota{be, cfg.QuotaBackendBytes}	// 创建 backendQuota 实例
}

// backendQuota.Available（） 方法中会将当前 BoltDB 中的数据量、此次请求的数据量之和与上
// 限阈值进行比较，从而决定此次请求是否触发限流，
func (b *BackendQuota) Available(v interface{}) bool {
	cost := b.Cost(v)
	// 如果没有变异请求，则通过
	if cost == 0 {
		return true
	}
	// TODO:可能优化后端。大小（）
	return b.be.Size()+int64(cost) < b.maxBackendBytes
}

// 请求的数据量是通过 Cost（）方法得到的，在其 Cost（）方法实现中，会根据请求的类型进行分类计算
func (b *BackendQuota) Cost(v interface{}) int {
	// Cost （）方法中根据请求的类型行分类处理
	switch r := v.(type) {
	case *pb.PutRequest:	//
		return costPut(r)
	case *pb.TxnRequest:
		return costTxn(r)
	case *pb.LeaseGrantRequest:	// Lease 相关的请求，
		return leaseOverhead
	default:	// 未知请求
		panic("unexpected cost")
	}
}

// costPut（）方法会计算请求的 Key 值、 Value 值和相关元数据的字节数之和，
func costPut(r *pb.PutRequest) int { return kvOverhead + len(r.Key) + len(r.Value) }

func costTxnReq(u *pb.RequestOp) int {
	r := u.GetRequestPut()
	if r == nil {
		return 0
	}
	return costPut(r)
}

// costTxn（） 方法则 是调用 costPut（）方法计算 TxnRequest. Success 和 T刀1Request.Failure
func costTxn(r *pb.TxnRequest) int {
	sizeSuccess := 0
	for _, u := range r.Success {
		sizeSuccess += costTxnReq(u)
	}
	sizeFailure := 0
	for _, u := range r.Failure {
		sizeFailure += costTxnReq(u)
	}
	// 与计算 r.Success 同理，计算 rFailure 的 数据量
	if sizeFailure > sizeSuccess {
		return sizeFailure
	}
	return sizeSuccess
}

func (b *BackendQuota) Remaining() int64 {
	return b.maxBackendBytes - b.be.Size()
}
